feat(mm): add page reclaim for file-backed memory pressure (rebased)#1007
Conversation
When the page allocator cannot satisfy a request, invoke a registered callback to evict clean file-backed page cache pages, then retry the allocation (up to 4 times). This prevents OOM during memory-intensive workloads like cargo build inside StarryOS. Changes: - axsync/src/mutex.rs: remove might_sleep() from try_lock_plain() and try_lock_nested() — try_lock is a single CAS, never blocks - axfs-ng/src/highlevel/file.rs: GLOBAL_CACHED_FILES registry, page_cache_reclaim() with per-file 64-page LRU eviction, RECLAIM_IN_PROGRESS reentrancy guard, evict_listeners for address-space unmapping callbacks - axalloc/src/lib.rs: register_page_reclaim_fn() / try_page_reclaim() API with SpinNoIrq guard released before callback invocation - axalloc/src/buddy_slab.rs: reclaim-then-retry loop in alloc_pages() (max 4 retries, reclaims max(16, num_pages) * 2 clean pages) - starry-kernel/src/entry.rs: register ax_fs::page_cache_reclaim as the page reclaim callback at kernel init Rebased onto upstream/dev (2026-05-28) incorporating PR rcore-os#987 (ax-alloc refactor: TLSF/buddy-slab backend simplification). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR introduces an allocator-triggered page-reclaim mechanism backed by file page-cache eviction, and refactors parts of the cached file I/O path. It also changes mutex wakeup semantics and guard sendability.
Changes:
- Add a reclaim callback API in
axalloc, invoke it on page-allocation failures, and register a filesystem-backed reclaim hook from kernel init. - Implement global page-cache reclaim by scanning cached files and evicting clean pages, plus refactor cached file read/write to share page-iteration logic.
- Adjust
RawMutexsemantics around lockdep subclassing, wakeup handoff, and guard marker traits.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 16 comments.
Show a summary per file
| File | Description |
|---|---|
| os/arceos/modules/axsync/src/mutex.rs | Changes mutex guard marker, lockdep subclass handling, and unlock wakeup/handoff logic |
| os/arceos/modules/axfs-ng/src/highlevel/file.rs | Adds page-cache reclaim + refactors cached I/O; modifies open/flag semantics and file-position initialization |
| os/arceos/modules/axalloc/src/lib.rs | Adds reclaim callback registration and invocation API |
| os/arceos/modules/axalloc/src/buddy_slab.rs | Retries page allocations after invoking reclaim callback |
| os/StarryOS/kernel/src/entry.rs | Registers filesystem reclaim callback during kernel init |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fn try_evict_clean_pages(&self, max: usize) -> usize { | ||
| let Some(mut cache) = self.page_cache.try_lock() else { | ||
| return 0; | ||
| }; |
| for &pn in to_evict[..cnt].iter() { | ||
| if let Some(page) = cache.pop(&pn) { | ||
| for listener in self.evict_listeners.lock().iter() { | ||
| (listener.listener)(pn, &page); | ||
| } | ||
| } | ||
| } |
- file.rs: restore upstream/dev baseline with all 11 bug fixes, then re-apply only the reclaim-specific additions (GLOBAL_CACHED_FILES, page_cache_reclaim, try_evict_clean_pages, RECLAIM_IN_PROGRESS, register_cached_file, is_new tracking). The original rebase was based on an old snapshot that lost upstream bug fixes. - mutex.rs: revert GuardMarker from GuardSend to GuardNoSend (MutexGuard must not cross thread boundaries) - mutex.rs: restore cfg-gated LockSubclass type alias for lockdep safety, use ax_lockdep::DEFAULT_LOCK_SUBCLASS in lock/try_lock calls Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
PR 审核总结
变更内容
本 PR 为物理页分配器增加了 page reclaim 路径:当页面分配失败时,调用注册的回调函数回收 file-backed page cache 中的 clean 页面,然后重试分配。这解决了 StarryOS 中 cargo build 等内存密集型工作负载导致 OOM 的问题。
共 5 个文件,3 个提交:
15f02b4— 核心 page reclaim 功能9f516db— 修复 Copilot 审查发现的问题(GuardNoSend、LockSubclass cfg-gating、to_flags/is_valid 恢复 Linux 兼容语义)6fd0bea— 补充 doc comment
代码分析
Mutex handoff 语义(mutex.rs):unlock() 改用 notify_one_with 直接将 ownership 交给被唤醒的等待者。关键:当等待队列为空时,notify_one_with 回调传入 0,owner_id.swap(0, Ordering::Release) 正确清除 owner_id——Copilot 此前关于 "owner_id 不会被清除" 的评论不成立(参见 axtask/src/wait_queue.rs:202 的 func(0) 分支)。try_lock 移除 might_sleep() 合理,因为 try_lock 是单次原子 CAS 永不阻塞。
Page reclaim 架构(file.rs):
RECLAIM_IN_PROGRESSAtomicBool 防止重入page_cache_reclaim()使用GLOBAL_CACHED_FILES.try_read()避免阻塞try_evict_clean_pages()使用page_cache.try_lock()避免死锁- 锁序正确:
page_cache→evict_listeners,且文档明确声明 listener 不得反向获取page_cache - 正确排除 tmpfs 文件(无 backing store,evict 会丢数据)
分配器集成(lib.rs + buddy_slab.rs):
try_page_reclaim()在调用回调前释放SpinNoIrq守卫,使回调在中断启用环境下运行- 重试循环 4 次,
reclaimed == 0时提前退出——合理 buddy_slab.rs中 dealloc 调整了 usages/inner 锁顺序(先更新 usage 再释放内存),影响极小
Copilot 审查意见处理:第二个提交 9f516db 已修复 Copilot 提出的全部关键问题:恢复 GuardNoSend、恢复 cfg-gated LockSubclass 和 DEFAULT_LOCK_SUBCLASS、恢复 to_flags 的 Linux 兼容映射、恢复 is_valid 的 RDONLY|TRUNC 允许逻辑、恢复 saturating_add 和 write_all。
本地验证
cargo fmt --check: ✅ PASScargo clippy --manifest-path os/arceos/modules/axalloc/Cargo.toml --features buddy-slab: ✅ PASScargo clippy --manifest-path os/arceos/modules/axfs-ng/Cargo.toml: ✅ PASScargo clippy --manifest-path os/arceos/modules/axsync/Cargo.toml: ✅ PASS- Head SHA
6fd0bea0a与 PR 一致
CI 状态
所有 GitHub Actions checks 显示 conclusion: skipped。这是因为 PR 来自 fork,CI 可能未完全触发。CI 失败非本 PR 导致。
重叠分析
已知限制
- 本 PR 未添加 reclaim 路径的专用测试用例。考虑到这是系统级内存管理特性,需要 QEMU + 实际内存压力环境才能有效测试,当前以
cargo build在 StarryOS 中不 OOM 作为验证标准可接受 to_evict数组硬编码 256 限制,实际场景足够
结论
实现设计合理,Copilot 审查意见已修复,代码质量好,可合并。
Powered by mimo-v2.5-pro
ZR233
left a comment
There was a problem hiding this comment.
本轮重点看了 allocator reclaim -> ax-fs-ng page cache -> Starry file-backed mapping listener 这一条路径的锁序和页面生命周期。
ax-sync::RawMutex 这部分我没有看到空等待队列导致 owner 卡死的问题:WaitQueue::notify_one_with() 在空队列时会调用 func(0),当前 unlock() 会把 owner_id 置回 0;handoff 给 waiter 的路径也能让等待者通过 is_owner(current_id) 返回。try_lock 去掉 might_sleep() 本身也符合非阻塞 CAS 的语义。
但 reclaim 路径有一个阻塞问题:try_evict_clean_pages() 在弹出 clean page 后立即调用 listener,之后让 PageCache drop 释放物理页。Starry 的 file mapping listener 在拿不到 AddrSpace 锁时会直接返回;这在 populate 内部 eviction 有后续 callback 兜底,但 allocator reclaim 没有对应的延期 unmap/保活机制。因此锁竞争时可能释放仍被用户页表映射的物理页,留下悬空 PTE。这个问题和死锁规避策略直接相关,需要先修复再合并。
本地验证:
cargo fmt --check:通过cargo xtask clippy --package ax-alloc:4/4 通过cargo xtask clippy --package ax-fs-ng:7/7 通过cargo xtask clippy --package ax-sync:3/3 通过cargo xtask clippy --package starry-kernel:13/13 通过
CI 状态:PR 当前 run_container/相关检查为 success,host/self-hosted 中部分 skipped;没有看到由本 PR 触发的 CI 失败。
重叠检查:origin/dev 上没有同等的 page reclaim API / page_cache_reclaim 实现;#804 已关闭且被本 PR supersede;#881 依赖本 PR 的能力但不重复实现;#997 release PR 可能后续有版本同步冲突风险,但不是同一语义实现。
| } | ||
| for &pn in to_evict[..cnt].iter() { | ||
| if let Some(page) = cache.pop(&pn) { | ||
| for listener in self.evict_listeners.lock().iter() { |
There was a problem hiding this comment.
这里不能在 page_cache 锁内直接调用 eviction listener 并随后让 page drop。当前 Starry 的 listener 在 FileBackendInner::register_listener() 中会先 aspace.try_lock(),拿不到地址空间锁时直接返回;这在普通 page_or_insert() eviction 里还能由 populate callback 延后处理,但 reclaim 路径没有保存 (pn, page) 也没有重试/回滚,函数返回后 clean page 的物理页会被释放,用户页表里可能仍然保留指向该物理页的 PTE。也就是说,这个“避免死锁”的 try_lock 分支会把死锁风险转成悬空映射/use-after-free。
建议把 reclaim 改成两阶段:在持有 page_cache 时只挑选并移出候选页,释放 page_cache 后调用 listener;并且 listener/回调需要能反馈是否完成了所有映射失效。若任何 listener 因锁竞争无法完成 unmap,就不能 drop 该 page(要放回 cache,或像 populate 的 PopulateCallback 一样延后到持有 AddrSpace 时再释放)。
There was a problem hiding this comment.
复核 cb849d4b1 后,这个问题仍然存在。新提交把 try_evict_clean_pages() 的返回值改成实际 pop 的数量,并补了 TODO,但当前代码仍然在 page_cache 锁内调用 listener,Starry 侧 listener 在 aspace.try_lock() 失败时仍然直接返回;reclaim 路径随后会 drop page 释放物理页,没有 PopulateCallback 那样的保活/延后 unmap 机制。
这里需要先让 reclaim eviction 能确认所有相关映射已经失效;如果 listener 因锁竞争或错误无法完成 unmap,就不能释放这个 page,需要放回 cache 或保留到后续能在持有 AddrSpace 时安全 unmap。
…tion Merge the two separate self-compilation docs (PR rcore-os#881 riscv64-only and PR rcore-os#973 riscv64+x86_64) into one unified document covering: - Architecture status table (riscv64 ✅, x86_64 ✅, aarch64 pending) - 5 shared blocking points (memory, tmpfs, linker, regex, workspace) - 6 x86_64-specific blocking points (PCI BAR, QEMU lock, ext4 compat bug series, SMP deadlock, init.sh POSIX, rustc MSRV, USB UVC) - 2 riscv64-specific blocking points (bitmap overflow obsoleted by PR rcore-os#987, dynamic RAM detection) - Script orchestration (prepare/compile/run/filter) - Test configuration (selfhost-manual/ out of CI path) - Known limitations and environment requirements Updated to reflect current state: - PR rcore-os#987 removed page-alloc-* features (bitmap overflow no longer exists) - PR rcore-os#1007 replaces rcore-os#804 for page reclaim - PR rcore-os#971 provides rsext4 cache (4 entries) - selfhost-manual/ directory structure (not normal/) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d correctness - buddy_slab: restore consistent lock order (inner then usages) in dealloc and dealloc_pages to match alloc_pages, preventing future ABBA deadlock - buddy_slab: document reclaim target scaling limitation for large contiguous allocations - file.rs: return actual evicted page count instead of scan count in try_evict_clean_pages; add TODO for lock-nesting removal - mutex.rs: document that unlock handoff depends on notify_one_with always calling the callback (including for empty queues with id=0) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ZR233
left a comment
There was a problem hiding this comment.
复核当前 head cb849d4b1 后,上一轮指出的 reclaim eviction 生命周期问题仍然是阻塞项。
本 PR 的主线仍然是:在 axalloc 中注册 page reclaim callback,页面分配失败时回收 ax-fs-ng file-backed page cache,并在 Starry init 时接入该回调;ax-sync::RawMutex 侧的 try_lock 非阻塞语义和 notify_one_with handoff 这部分我没有看到新的阻塞问题。新提交主要补了 buddy_slab 锁序说明、把 try_evict_clean_pages() 返回值改成实际 evicted 数量,并补了 TODO,但没有改变 reclaim 路径的页面释放时机。
阻塞问题仍在 try_evict_clean_pages():它从 page_cache 中 pop clean page 后立即调用 listener,之后让 page drop 释放物理页。Starry 的 file mapping listener 在拿不到 AddrSpace 锁时会直接返回;普通 page_or_insert() eviction 有 PopulateCallback 保活并延后 unmap,但 allocator reclaim 没有等价机制。因此锁竞争时仍可能释放还被用户页表映射的 frame,留下悬空 PTE / use-after-free。已在原有 inline thread 里补充当前 head 的复核结论,保留该线程未解决。
建议修复方向:reclaim eviction 需要能确认所有相关映射已经失效后才释放 page。可以改成两阶段回收,并让 listener/回调返回是否完成 unmap;若任何 listener 因锁竞争无法完成,需要把 page 放回 cache,或像 populate callback 一样保留到后续持有 AddrSpace 时再释放。
本地验证:
cargo fmt --check:通过cargo xtask clippy --package ax-alloc --package ax-fs-ng --package ax-sync --package starry-kernel:27/27 通过
CI 状态:当前 gh pr checks 中没有看到失败项;多项 run_container 检查已通过或 skipped,但 Run clippy / run_container、Test starry x86_64 qemu / run_container、Test starry riscv64 qemu / run_container、Test starry aarch64 qemu / run_container 在复核时仍为 pending,因此远端 CI 尚未完整结束。
重复/重叠检查:origin/dev 上未发现同等的 register_page_reclaim_fn / try_page_reclaim / page_cache_reclaim / GLOBAL_CACHED_FILES 实现;#804 已关闭且由本 PR supersede;#881 依赖 #1007 提供的能力但不重复实现;#997 是 release 同步 PR,可能有后续版本同步冲突风险,但不是同一语义实现。
旧 review threads 处理:已将 GuardNoSend、LockSubclass、open flags、读写路径等已经修复或过时的 14 个线程标记 resolved;保留 page-cache listener / reclaim 生命周期相关线程未解决。
When the reclaim path evicts a clean page and the listener cannot acquire the AddrSpace lock (try_lock contention), the page was previously dropped immediately — freeing the physical frame while PTEs still referenced it, creating a use-after-free. Fix: change EvictListenerFn to return bool. Listeners return false when they cannot complete the unmap. try_evict_clean_pages puts the page back into the LRU cache instead of dropping it. The page survives until the next reclaim attempt when the lock is available. The LRU-eviction path (evict_cache, called during page_or_insert) ignores the return value because the populate process that triggers it holds AddrSpace and handles unmap via PopulateCallback. Fixes the blocking review finding from ZR233 on PR rcore-os#1007. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
PR 审核总结
变更内容
本 PR 为物理页分配器增加 page reclaim 路径:当页面分配失败时,调用注册的回调函数回收 file-backed page cache 中的 clean 页面,然后重试分配(最多 4 次),防止 StarryOS 中 cargo build 等内存密集型工作负载导致 OOM。
共 5 个提交,6 个文件:
15f02b4— 核心 page reclaim 功能9f516db— 修复 Copilot 审查发现的问题(GuardNoSend、LockSubclass cfg-gating、to_flags/is_valid 恢复 Linux 兼容语义)6fd0bea— 补充 init_percpu_slab doc commentcb849d4— buddy_slab 锁序修正、try_evict 返回实际 evict 数量、mutex unlock handoff 文档87c05db— 修复 reclaim eviction 悬挂 PTE 问题(ZR233 阻塞项)
实现逻辑
Mutex handoff 语义(mutex.rs):unlock() 改用 notify_one_with 直接将 ownership 交给被唤醒的等待者。空队列时 notify_one_with 回调传入 0,owner_id.swap(0, Release) 正确清除 owner_id。try_lock 移除 might_sleep() 合理——try_lock 是单次原子 CAS 永不阻塞,可在 allocator reclaim 路径的中断禁用上下文中使用。
Page reclaim 架构(file.rs):
RECLAIM_IN_PROGRESSAtomicBool 防止重入page_cache_reclaim()使用GLOBAL_CACHED_FILES.try_read()避免阻塞try_evict_clean_pages()使用page_cache.try_lock()避免死锁- 正确排除 tmpfs 文件(无 backing store,evict 会丢数据)
- EvictListener 改为返回
bool:listener 返回false时,page 放回 LRU cache 而非 drop,避免释放仍被用户页表映射的物理帧
Dangling PTE 修复(file.rs + backend/file.rs):
EvictListenerFn签名改为Fn(u32, &PageCache) -> bool- Starry 的 file mapping listener 在
AddrSpace::try_lock()竞争失败时返回false try_evict_clean_pages()在任一 listener 返回false时将 page 放回 cache- LRU-eviction 路径(
evict_cache)忽略返回值——populate 进程持有 AddrSpace,通过PopulateCallback处理 unmap - 此修复正确解决了 ZR233 提出的阻塞问题
分配器集成(lib.rs + buddy_slab.rs):
try_page_reclaim()调用回调前释放SpinNoIrq守卫,使回调在中断启用环境下运行- 重试循环 4 次,
reclaimed == 0时提前退出——合理 buddy_slab.rs修正了dealloc/dealloc_pages的锁序(inner → usages),与alloc_pages一致
本地验证
cargo fmt --check:✅ 通过cargo xtask clippy --package ax-alloc --package ax-fs-ng --package ax-sync --package starry-kernel:27/27 通过- Head SHA
87c05db77与 PR 最新一致
CI 状态
PR 当前 mergeable_state=blocked。远端 CI 部分检查已通过或 skipped;由 fork 提交的 PR 可能未完全触发 CI。当前环境未发现由本 PR 导致的 CI 失败。
重叠分析
- PR #804(同一作者的 v2 版本)已关闭未合并,本 PR 明确 supersede #804
origin/dev上未发现与register_page_reclaim_fn/page_cache_reclaim/GLOBAL_CACHED_FILES冲突的实现- 在 dev 分支上未发现与 page reclaim 冲突的 open PR(#1040 xattr、#1039 qperf 均不相关)
- #881 依赖本 PR 的能力但不重复实现;#997 release PR 可能后续有版本同步冲突风险,但不是同一语义实现
测试覆盖
本 PR 未添加专用测试用例。鉴于这是系统级内存管理特性,需要 QEMU + 实际内存压力环境才能有效测试,当前以 cargo build 在 StarryOS 中不 OOM 作为验证标准可接受。PR 中也未新增 apps 测试,无需执行 Starry QEMU 验证。
已解决的审查问题
- Copilot 审查意见(GuardNoSend、LockSubclass、to_flags、is_valid、saturating_add、write_all)已在提交 2 中修复,相关 inline thread 位于 outdated diff
- ZR233 阻塞项(dangling PTE)已在提交 5 中修复:EvictListener 返回 false 时 page 放回 cache,不再释放仍被映射的物理帧
已知限制与后续工作
to_evict数组硬编码 256 限制,实际场景足够try_evict_clean_pages中page_cache→evict_listeners的锁嵌套有潜在死锁风险(TODO 已标注),未来可重构为先收集 evicted pages 再通知 listeners- 未添加 reclaim 路径的集成测试,建议后续补充 QEMU 内存压力测试
结论
实现设计合理,ZR233 的 dangling PTE 阻塞问题已在最新提交中正确修复,Copilot 审查意见已处理,代码质量好,可合并。
Powered by mimo-v2.5-pro
There was a problem hiding this comment.
PR 审核总结
变更内容
本 PR 为物理页分配器增加 page reclaim 路径:当页面分配失败时,调用注册的回调函数回收 file-backed page cache 中的 clean 页面,然后重试分配(最多 4 次),防止 StarryOS 中 cargo build 等内存密集型工作负载导致 OOM。
共 5 个提交,6 个文件:
15f02b4— 核心 page reclaim 功能9f516db— 修复 Copilot 审查发现的问题(GuardNoSend、LockSubclass cfg-gating、to_flags/is_valid 恢复 Linux 兼容语义)6fd0bea— 补充 init_percpu_slab doc commentcb849d4— buddy_slab 锁序修正、try_evict 返回实际 evict 数量、mutex unlock handoff 文档87c05db— 修复 reclaim eviction 悬挂 PTE 问题(ZR233 阻塞项)
实现逻辑
Mutex handoff 语义(mutex.rs):unlock() 改用 notify_one_with 直接将 ownership 交给被唤醒的等待者。验证了 axtask/src/wait_queue.rs:193 的 notify_one_with 实现:空队列时确实调用 func(0),owner_id.swap(0, Release) 正确清除 owner_id。try_lock 移除 might_sleep() 合理——try_lock 是单次原子 CAS 永不阻塞,可在 allocator reclaim 路径的中断禁用上下文中使用。
Page reclaim 架构(file.rs):
RECLAIM_IN_PROGRESSAtomicBool 防止重入page_cache_reclaim()使用GLOBAL_CACHED_FILES.try_read()避免阻塞try_evict_clean_pages()使用page_cache.try_lock()避免死锁- 正确排除 tmpfs 文件(无 backing store,evict 会丢数据)
- EvictListener 改为返回
bool:listener 返回false时,page 放回 LRU cache 而非 drop,避免释放仍被用户页表映射的物理帧
Dangling PTE 修复(file.rs + backend/file.rs):
EvictListenerFn签名改为Fn(u32, &PageCache) -> bool- Starry 的 file mapping listener 在
AddrSpace::try_lock()竞争失败时返回false try_evict_clean_pages()在任一 listener 返回false时将 page 放回 cache(MRU 位置,延迟重试)- LRU-eviction 路径(
evict_cache)忽略返回值——populate 进程持有 AddrSpace,通过PopulateCallback处理 unmap - 此修复正确解决了 ZR233 提出的阻塞问题
分配器集成(lib.rs + buddy_slab.rs):
try_page_reclaim()调用回调前释放SpinNoIrq守卫,使回调在中断启用环境下运行- 重试循环 4 次,
reclaimed == 0时提前退出——合理 buddy_slab.rs修正了alloc_pages/alloc_pages_lowmem之后dealloc/dealloc_pages的锁序(inner → usages),与alloc_pages一致
本地验证
cargo fmt --check:✅ 通过cargo clippy --manifest-path os/arceos/modules/axalloc/Cargo.toml --features buddy-slab:✅ 通过cargo clippy --manifest-path os/arceos/modules/axsync/Cargo.toml:✅ 通过cargo clippy --manifest-path os/arceos/modules/axfs-ng/Cargo.toml:✅ 通过cargo xtask clippy --package ax-alloc --package ax-fs-ng --package ax-sync --package starry-kernel:27/27 通过- Head SHA
87c05db77与 PR 最新一致
CI 状态
PR 当前 mergeable_state=blocked。远端 CI 部分检查已通过或 skipped;由 fork 提交的 PR 可能未完全触发 CI。当前未发现由本 PR 导致的 CI 失败。
重叠分析
- PR #804(同一作者的 v2 版本)已关闭未合并,本 PR 明确 supersede #804
origin/dev上未发现register_page_reclaim_fn/page_cache_reclaim/GLOBAL_CACHED_FILES的等价实现- PR #881(self-compilation)依赖本 PR 的能力但不重复实现,属于 partial-overlap(互补依赖关系)
- #1040(xattr)、#1039(qperf)均不相关
- 无冲突风险的 open PR
测试覆盖
本 PR 未添加专用测试用例。鉴于这是系统级内存管理特性,需要 QEMU + 实际内存压力环境才能有效测试,当前以 cargo build 在 StarryOS 中不 OOM 作为验证标准可接受。PR 中也未新增 apps 测试,无需执行 Starry QEMU app 验证。
已解决的审查问题
- Copilot 审查意见(GuardNoSend、LockSubclass、to_flags、is_valid、saturating_add、write_all)已在提交 2 和 4 中修复
- ZR233 阻塞项(dangling PTE)已在提交 5 中正确修复:EvictListener 返回 false 时 page 放回 cache,不再释放仍被映射的物理帧
已知限制与后续工作(非阻塞)
to_evict数组硬编码 256 限制,实际场景足够try_evict_clean_pages中page_cache→evict_listeners的锁嵌套有潜在死锁风险(TODO 已标注),未来可重构为先收集 evicted pages 再通知 listeners- 未添加 reclaim 路径的集成测试,建议后续补充 QEMU 内存压力测试
GLOBAL_CACHED_FILES向量随文件打开增长,但register_cached_file已做 dead-ref 清理
结论
实现设计合理,锁序正确,ZR233 的 dangling PTE 阻塞问题已在最新提交中正确修复,Copilot 审查意见已处理,代码质量好,无阻塞问题,可合并。
Powered by mimo-v2.5-pro
…oking listeners Calling eviction listeners while holding the page_cache lock creates a deadlock risk: if any listener tries to acquire page_cache (directly or indirectly), the lock ordering violation causes a deadlock. This restructures try_evict_clean_pages() into two phases: 1. Under page_cache lock: identify and pop clean pages into a local Vec 2. Outside page_cache lock: invoke listeners; pages whose listeners cannot confirm unmap (e.g., AddrSpace lock contention) are re-inserted into the cache to prevent use-after-free of physical frames with still-live PTEs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5461a3a to
733add3
Compare
There was a problem hiding this comment.
PR 审核总结(合并后复核)
变更内容
本 PR 为物理页分配器增加 page reclaim 路径:当页面分配失败时,调用注册的回调函数回收 file-backed page cache 中的 clean 页面,然后重试分配(最多 4 次),防止 StarryOS 中 cargo build 等内存密集型工作负载导致 OOM。
共 6 个提交,6 个文件(+260/-66):
15f02b4— 核心 page reclaim 功能9f516db— 修复 Copilot 审查发现的问题6fd0bea— 补充 init_percpu_slab doc commentcb849d4— buddy_slab 锁序修正、try_evict 返回实际 evict 数量87c05db— 修复 reclaim eviction 悬挂 PTE 问题733add3— 两阶段 eviction:释放 page_cache 锁后再调用 listeners
实现逻辑分析
Mutex handoff 语义(mutex.rs):unlock() 改用 notify_one_with 直接将 ownership 交给被唤醒的等待者。验证了 axtask/src/wait_queue.rs 的 notify_one_with 实现:空队列时确实调用 func(0),owner_id.swap(0, Release) 正确清除 owner_id。try_lock 移除 might_sleep() 合理——try_lock 是单次原子 CAS 永不阻塞,可在 allocator reclaim 路径的中断禁用上下文中使用。注释中也明确了对 notify_one_with 行为契约的依赖。
Page reclaim 架构(file.rs):
RECLAIM_IN_PROGRESSAtomicBool 防止重入,使用 RAIIReclaimGuard确保异常退出时也清除标志page_cache_reclaim()使用GLOBAL_CACHED_FILES.try_read()避免阻塞try_evict_clean_pages()使用page_cache.try_lock()避免死锁- 正确排除 tmpfs 文件(无 backing store,evict 会丢数据)
- 两阶段 eviction:Phase 1 在
page_cache锁下收集 clean pages 到本地 Vec,Phase 2 在锁外调用 listeners。这消除了之前page_cache→evict_listeners锁嵌套的潜在死锁风险 - EvictListener 返回
bool:listener 返回false时,page 放回 LRU cache 而非 drop,避免释放仍被用户页表映射的物理帧
Dangling PTE 修复(file.rs + backend/file.rs):
- Starry 的 file mapping listener 在
AddrSpace::try_lock()竞争失败时返回false try_evict_clean_pages()在任一 listener 返回false时将 page 放回 cache- LRU-eviction 路径(
evict_cache)忽略返回值——populate 进程持有 AddrSpace,通过PopulateCallback处理 unmap - 注释清楚解释了两条路径的不同行为
分配器集成(lib.rs + buddy_slab.rs):
try_page_reclaim()调用回调前释放SpinNoIrq守卫,使回调在中断启用环境下运行- 重试循环 4 次,
reclaimed == 0时提前退出——合理 buddy_slab.rs修正了dealloc/dealloc_pages的锁序(inner → usages),与alloc_pages一致,并添加了锁序说明注释
本地验证
cargo fmt --check:✅ 通过cargo xtask clippy --package ax-alloc --package ax-fs-ng --package ax-sync --package starry-kernel:27/27 通过- Head SHA
733add3ab与 PR 最新一致
CI 状态
PR 当前已合并(merge commit 6fce64bc6)。CI 检查大部分为 skipped,因为 PR 来自 fork,CI 未完全触发。未发现由本 PR 导致的 CI 失败。
重叠分析
- PR #804(同一作者的 v2 版本)已关闭未合并,本 PR 明确 supersede #804
origin/dev上未发现register_page_reclaim_fn/page_cache_reclaim/GLOBAL_CACHED_FILES的等价实现- PR #881 依赖本 PR 的能力但不重复实现,属于 partial-overlap(互补依赖关系)
- 无冲突风险的 open PR
测试覆盖
本 PR 未添加专用测试用例,也未新增 apps 测试。鉴于这是系统级内存管理特性,需要 QEMU + 实际内存压力环境才能有效测试,当前以 cargo build 在 StarryOS 中不 OOM 作为验证标准可接受。无需执行 Starry QEMU apps 验证。
已解决的审查问题
- Copilot 审查意见(GuardNoSend、LockSubclass、to_flags、is_valid、saturating_add、write_all)已在后续提交中修复。最终 PR head 保留了原始
read_at/write_at_locked实现,未引入 Copilot 标记的回归 - ZR233 阻塞项(dangling PTE)已在提交 5 中正确修复:EvictListener 返回 false 时 page 放回 cache
- ZR233 最终在
87c05db后批准,随后补充了两阶段 eviction(733add3)进一步消除死锁风险
已知限制与后续工作(非阻塞)
to_evict数组硬编码 256 限制,实际场景足够- 未添加 reclaim 路径的集成测试,建议后续补充 QEMU 内存压力测试
GLOBAL_CACHED_FILES向量随文件打开增长,但register_cached_file已做 dead-ref 清理
结论
实现设计合理,锁序正确,两阶段 eviction 消除了死锁风险,dangling PTE 问题已正确修复。代码质量好,已合并。
Powered by mimo-v2.5-pro
…tion Merge the two separate self-compilation docs (PR rcore-os#881 riscv64-only and PR rcore-os#973 riscv64+x86_64) into one unified document covering: - Architecture status table (riscv64 ✅, x86_64 ✅, aarch64 pending) - 5 shared blocking points (memory, tmpfs, linker, regex, workspace) - 6 x86_64-specific blocking points (PCI BAR, QEMU lock, ext4 compat bug series, SMP deadlock, init.sh POSIX, rustc MSRV, USB UVC) - 2 riscv64-specific blocking points (bitmap overflow obsoleted by PR rcore-os#987, dynamic RAM detection) - Script orchestration (prepare/compile/run/filter) - Test configuration (selfhost-manual/ out of CI path) - Known limitations and environment requirements Updated to reflect current state: - PR rcore-os#987 removed page-alloc-* features (bitmap overflow no longer exists) - PR rcore-os#1007 replaces rcore-os#804 for page reclaim - PR rcore-os#971 provides rsext4 cache (4 entries) - selfhost-manual/ directory structure (not normal/) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…tion Merge the two separate self-compilation docs (PR rcore-os#881 riscv64-only and PR rcore-os#973 riscv64+x86_64) into one unified document covering: - Architecture status table (riscv64 ✅, x86_64 ✅, aarch64 pending) - 5 shared blocking points (memory, tmpfs, linker, regex, workspace) - 6 x86_64-specific blocking points (PCI BAR, QEMU lock, ext4 compat bug series, SMP deadlock, init.sh POSIX, rustc MSRV, USB UVC) - 2 riscv64-specific blocking points (bitmap overflow obsoleted by PR rcore-os#987, dynamic RAM detection) - Script orchestration (prepare/compile/run/filter) - Test configuration (selfhost-manual/ out of CI path) - Known limitations and environment requirements Updated to reflect current state: - PR rcore-os#987 removed page-alloc-* features (bitmap overflow no longer exists) - PR rcore-os#1007 replaces rcore-os#804 for page reclaim - PR rcore-os#971 provides rsext4 cache (4 entries) - selfhost-manual/ directory structure (not normal/) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* docs(starry): rebase self-compilation PR onto current upstream/dev Rebuild PR #881 on current upstream/dev (8f95b7a) after significant dependency changes: - PR #987 removed page-alloc-* features; drop obsolete page-alloc-64g passthrough and axalloc Cargo.toml injection - PR #992 added .tracepoint/.kallsyms sections; use sed-based injection in test scripts instead of cat-overwrite to preserve these sections - fsopen/fspick/open_tree ENOSYS fix already present on upstream/dev - phys-memory-size = 0x2_0000_0000 already present on upstream/dev Changes: - ext_linker.ld: add PROVIDE(_ex_table_start/end = 0) for self-compile environments where linker.x is not passed via rustflags - scripts/self-compile.sh: host-side automation (cleaned of dead code) - docs/starryos-self-compilation.md: updated for post-PR #987 architecture - test-suit/starryos/normal/qemu-selfhost/: test group with inner self-compile.sh (sed-based linker fix, preserves existing sections) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(starry): add missing driver features and env vars to selfhost build config - Add ax-driver/pci, ax-driver/virtio-blk, ax-driver/virtio-net and other virtio driver features to qemu-selfhost build config, matching the standard qemu-smp1 build config. Without these the seed kernel cannot access the Debian rootfs via virtio-blk-pci. - Add AX_IP/AX_GW env vars for network stack initialization. - Remove dead INJECT_SCRIPT variable from scripts/self-compile.sh. - Add --features to test-suit inner cargo build for feature parity. - Fix test-selfhost-check to actually detect missing tools (was always passing CHECK_DONE due to || true). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(test): remove stale ax-driver/pci feature and fix success_regex escape Two fixes for CI failures: 1. Remove `ax-driver/pci` from build config features — this umbrella feature was removed from upstream/dev by PR #937 (refactor(driver): move static probes to platform-owned registration). PCI dependencies are now always available; individual device features handle their own chains. 2. Fix success_regex TOML literal string — single-quoted TOML strings do not process backslash escapes, so '\\s' was interpreted as literal \\s by the regex engine instead of \s (whitespace class). Changed to double-quoted TOML string "\\s" to produce the correct regex pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(test): move selfhost tests out of normal/ CI path to selfhost-manual/ The qemu-selfhost test group had a build-riscv64gc-unknown-none-elf.toml in test-suit/starryos/normal/, which caused the CI test runner to discover and attempt to run these tests. The selfhost tests require a 12 GB Debian rootfs image that is not available in CI (not published to the tgosimages release), causing "HTTP 404 Not Found" on rootfs download and blocking ALL starry riscv64 tests. Moved test infrastructure to test-suit/starryos/selfhost-manual/, outside the normal/ directory that CI scans. Updated documentation to reflect the new path and manual test invocation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(starry): consolidate riscv64 + x86_64 self-compilation documentation Merge the two separate self-compilation docs (PR #881 riscv64-only and PR #973 riscv64+x86_64) into one unified document covering: - Architecture status table (riscv64 ✅, x86_64 ✅, aarch64 pending) - 5 shared blocking points (memory, tmpfs, linker, regex, workspace) - 6 x86_64-specific blocking points (PCI BAR, QEMU lock, ext4 compat bug series, SMP deadlock, init.sh POSIX, rustc MSRV, USB UVC) - 2 riscv64-specific blocking points (bitmap overflow obsoleted by PR #987, dynamic RAM detection) - Script orchestration (prepare/compile/run/filter) - Test configuration (selfhost-manual/ out of CI path) - Known limitations and environment requirements Updated to reflect current state: - PR #987 removed page-alloc-* features (bitmap overflow no longer exists) - PR #1007 replaces #804 for page reclaim - PR #971 provides rsext4 cache (4 entries) - selfhost-manual/ directory structure (not normal/) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * docs(starry): update aarch64 status — boot verified, rootfs ready aarch64 self-compilation infrastructure verified: - Seed kernel builds (plat_dyn=true, PIE target) - Debian arm64 rootfs prepared (debootstrap + rustc nightly) - QEMU boots to shell prompt with rootfs mounted - Full compilation pending (TCG emulation, estimated 4-8h) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(self-compile): add trap cleanup, heredoc comments, remove redundant -n guard Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(selfhost): remove stale ax-driver/pci from inner self-compile.sh PR #937 removed the ax-driver/pci feature. The inner guest script still referenced it, causing cargo build to fail inside QEMU. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(selfhost): use env bash shebang and unify test memory to 8G - Change shebang from /usr/bin/bash to /usr/bin/env bash for portability - Bump test-selfhost-check memory from 2G to 8G to match axconfig phys-memory-size Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(starry-test): restructure selfhost-manual into qemu-selfhost wrapper The build-riscv64gc-unknown-none-elf.toml was at the group root as a file, but xtask's test discovery only scans directories for build-*.toml files. This made selfhost-manual test cases invisible to . Move the build config and two test cases into a qemu-selfhost wrapper directory, matching the pattern used by qemu-smp1, qemu-smp4, etc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(starry): fix directory diagram and CLI flag in self-compilation docs - Add qemu-selfhost/ wrapper level to directory tree diagram - Fix --test-suite to --test-group in CLI invocation example Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(self-compile): revert script to dev version — avoid overriding PR #1076 improvements The PR #881 branch carried an older version of scripts/self-compile.sh that would regress PR #1076's improvements if merged after: - tmpfs setup (registry + workspace) - rootfs cleanup function with idempotent guard - jemalloc debug output filtering - QEMU 12G memory (vs 8G) - per-arch QEMU_NET_DEV variable (vs hardcoded virtio-net-pci) - axalloc Cargo.toml and filter-workspace.sh injection - sudo prerequisite check PR #1076's version already handles riscv64, x86_64, and aarch64 correctly. PR #881 now inherits it without modification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(self-compile): drop all script changes — inherit upstream/dev version PR #881 should not modify scripts/self-compile.sh. The self-compilation script improvements are handled by PR #1076. PR #881 only adds: - Documentation (docs/starryos-self-compilation.md) - Linker PROVIDE fallback for _ex_table_* symbols - riscv64 selfhost test infrastructure (selfhost-manual/ test group) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(review): address pr-review BLOCK findings 1. linker.ld: restore .tracepoint, .kallsyms sections and original dyndbg symbol names (__start___dyndbg/__stop___dyndbg) that were inadvertently dropped during rebase. Keep the PROVIDE lines. 2. Guest self-compile.sh: add [ -f ext_linker.ld ] guard before grep+sed block — the file was deleted upstream, and sed -i on a missing file would fail under set -euo pipefail. 3. docs: update PROVIDE fix reference from ext_linker.ld to linker.ld since ext_linker.ld was merged into linker.ld upstream. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(selfhost): migrate test suite to apps/starry/selfhost ZR233 feedback: "测例ci 耗时过多,请移至 apps 目录" Move selfhost test cases from test-suit/starryos/selfhost-manual/ to apps/starry/selfhost/ to follow the standard Starry app pattern. - apps/starry/selfhost/build-riscv64gc-unknown-none-elf.toml - apps/starry/selfhost/selfhost-full-kernel/ (full compilation, 7200s) - apps/starry/selfhost/test-selfhost-check/ (quick tool check, 120s) Usage: cargo xtask starry app qemu --arch riscv64 --app-case selfhost/test-selfhost-check Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(selfhost): increase QEMU RAM from 8GB to 12GB to prevent OOM Observed OOM during self-compilation with 8GB. Changes: - qemu configs: -m 8G → -m 12G in both test cases - build config: add axconfig_overrides with phys-memory-size=0x3_0000_0000 - docs: update all 8G references to 12G, add OOM warning - host self-compile.sh already uses 12G (inherited from PR #1076) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(selfhost): bump guest tmpfs from 8G to 12G to match QEMU memory The QEMU config and build axconfig_overrides were already updated to 12G. This makes the guest tmpfs mount consistent, preventing OOM during the cargo build inside the guest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(starry): fix CLI flag and clarify host vs app case RAM requirements ZR233 review feedback: 1. Fix --app-case to -t/--test-case (the actual CLI parameter name) 2. Add dependency note for PR #1076 (which upgrades self-compile.sh to 12G) 3. Distinguish app case path (12G via build config + qemu config) from host script path (requires PR #1076 merge for 12G support) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: CI Test <noreply@starryos.org> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Add a physical-page reclaim path: when the allocator cannot satisfy a page request, it invokes a registered callback to evict clean file-backed page cache pages, then retries the allocation. This prevents OOM during memory-intensive workloads like
cargo buildinside StarryOS.1.
might_sleep()removal fromtry_lock()Remove
might_sleep()from bothtry_lock_plain()andtry_lock_nested().try_lockis a single atomic CAS that never blocks or sleeps (equivalent to Linuxmutex_trylock). The reclaim path runs with IRQs disabled (inside the allocator spinlock), sotry_lock()must be usable there.2. Page cache eviction + global file registry
GLOBAL_CACHED_FILESregistry: every file with a page cache is registered viaregister_cached_file()page_cache_reclaim(): walks all files, evicting clean (non-dirty) pages. Usestry_lockthroughout to avoid deadlock.RECLAIM_IN_PROGRESSAtomicBooltry_evict_clean_pages()evict_listenersusesspin::Mutex(nomight_sleep) for safety from atomic context3. Allocator reclaim API + init hook
register_page_reclaim_fn()/try_page_reclaim()API in axallocpage_cache_reclaimas the allocator callback during kernel initReview fixes (2026-05-31)
bool— if any listener cannot confirm unmap (e.g., AddrSpace lock contention), the page is re-inserted into cache instead of being freedtry_evict_clean_pages()now releasespage_cachelock before invoking listeners, eliminating the deadlock riskRebase notes
Rebased onto upstream/dev (2026-05-28), incorporating PR #987 (ax-alloc refactor: TLSF/buddy-slab backend simplification).
Supersedes: PR #804
Files changed (7)
os/arceos/modules/axsync/src/mutex.rsos/arceos/modules/axfs-ng/src/highlevel/file.rsos/arceos/modules/axalloc/src/lib.rsos/arceos/modules/axalloc/src/buddy_slab.rsos/StarryOS/kernel/src/entry.rsos/StarryOS/kernel/src/mm/aspace/backend/file.rsTest plan
cargo check -p ax-alloc -p ax-sync -p ax-fs-ng -p starry-kernel: PASScargo clippy -p ax-alloc -p ax-sync -p ax-fs-ng: PASScargo test -p rsext4 --lib: 71/71 PASScargo test -p rsext4 --test crc_integrity: 16/16 PASSrustfmt --edition 2024 --check: PASS🤖 Generated with Claude Code